﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RPGProject.Data_Structures;
using RPGProject.GameWorld.GameObjects;
using RPGProject.IOBuffers;
using RPGProject.LoopSystem;
namespace RPGProject.GameWorld
{
    sealed partial class World : IPropertyRetrievable
    {

        /// <summary>
        /// Swaps to objects in the active level.
        /// </summary>
        /// <param name="x1">The first x coordinate.</param>
        /// <param name="y1">The first y coordinate.</param>
        /// <param name="x2">The second x coordinate.</param>
        /// <param name="y2">The second y coordinate.</param>
        public void SwapActiveLevel(int x1, int y1, int x2, int y2)
        {
            GameObject go1 = this.activeLevel[x1][y1];
            GameObject go2 = this.activeLevel[x2][y2];

            go1["x"] = x2;
            go1["y"] = y2;
            go2["x"] = x1;
            go2["y"] = y1;

            this.PutGameObject(go1);
            this.PutGameObject(go2);
        }
        /// <summary>
        /// Puts a GameObject on the   The GameObject should contain its own location.
        /// </summary>
        /// <param name="go">The GameObject to place on the </param>
        public void PutGameObject(GameObject go)
        {
            this.activeLevel[(int)go["x"]][(int)go["y"]] = go;
        }
        /// <summary>
        /// Puts a MapTile on the   The MapTile should contain its own location.
        /// </summary>
        /// <param name="mapTile">The MapTile to place on the </param>
        public void PutMapTile(MapTile mapTile)
        {
            this.backgroundLevel[mapTile.x][mapTile.y] = mapTile;
        }
        /// <summary>
        /// Puts a SpawnPoint in the list of spawn points.  Should contain its name.
        /// </summary>
        /// <param name="spawnPoint">The SpawnPoint to be listed.</param>
        public void PutSpawnPoint(SpawnPoint spawnPoint)
        {
            this.spawnPoints.Add(spawnPoint.name, spawnPoint);
        }

        /// <summary>
        /// Spawns a character at the spawn point named.
        /// </summary>
        /// <param name="character">The character to be placed.</param>
        /// <param name="p">SpawnPoint name to be spawned at.</param>
        /// <returns>Whether the spawn was successful or not.</returns>
        public bool SpawnCharacter(GameObject gameObject, String p)
        {
            SpawnPoint sp = this.spawnPoints[p];
            if (this.activeLevel[sp.x][sp.y].TypeId == "null")
            {
                this.activeObjects[(String)gameObject["name"]] = gameObject;
                String type = gameObject.TypeId + "count";
                if (this.properties.ContainsKey(type))
                    this[type] = (int)this[type] + 1;
                else
                    this[type] = 1;
                gameObject["x"] = sp.x;
                gameObject["y"] = sp.y;
                this.PutGameObject(gameObject);
                return true;
            }
            else
                return false;
        }
        /// <summary>
        /// Removes GameObjects and decrements their count in the properties array.
        /// </summary>
        /// <param name="source"></param>
        private void RemoveGameObject(GameObject source)
        {
            if (this.activeLevel[(int)source["x"]][(int)source["y"]].Equals(source))
            {
                this.PutGameObject(new NullGameObject((int)source["x"], (int)source["y"]));
                if (source is Character)
                {
                    Character c = source as Character;
                    String type = c.TypeId + "count";
                    if (this.properties.ContainsKey(type))
                        this[type] = (int)this[type] - 1;
                    else
                        this[type] = 0;
                    this.activeObjects.Remove((String)c["name"]);
                }
            }
        }
        /// <summary>
        /// Gets the properties for the world.
        /// </summary>
        public Dictionary<String, Object> Properties
        {
            get { return this.properties; }
        }
        /// <summary>
        /// String indexer referring to the properties.  Also updates them as such, and notifies.
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        public object this[String s]
        {
            get
            {
                if (this.Properties.ContainsKey(s))
                    return this.Properties[s];
                else if (this.activeObjects.ContainsKey(s))
                    return this.activeObjects[s];
                else
                    return null;
            }
            set
            {
                this.Properties[s] = value;
                this.OnUpdate(s, value);
            }
        }
        /// <summary>
        /// Update Helper.
        /// </summary>
        /// <param name="s"></param>
        /// <param name="value"></param>
        private void OnUpdate(string s, object value)
        {
            foreach (OnPropUpdate opu in this.onUpdates)
            {
                opu(s, value);
            }
        }
        /// <summary>
        /// From IPropertyRetrievable.
        /// </summary>
        /// <param name="lit"></param>
        /// <returns></returns>
        public object this[PropertyLiteral lit]
        {
            get
            {
                if (lit.Size == 1)
                    return this[lit.Front];
                else
                    return ((IPropertyRetrievable)this[lit.Front])[lit.Next];
            }
            set
            {
                if (lit.Size == 1)
                    this[lit.Front] = value;
                else
                    ((IPropertyRetrievable)this[lit.Front])[lit.Next] = value;
            }
        }
        /// <summary>
        /// Adds a stat to be rendered and updated.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="stat"></param>
        public void AddStat(String name, StatOutputBuffer.Stat stat)
        {
            this.stats.AddStat(name, stat);
        }
        /// <summary>
        /// Gives out the delegate for the stat buffer.
        /// </summary>
        /// <returns></returns>
        public OnPropUpdate GetStatUpdate()
        {
            return this.stats.OnPropUpdate;
        }
        /// <summary>
        /// Adds a script from file.
        /// </summary>
        /// <param name="filePath"></param>
        public void AddScript(String filePath)
        {
            this.scripts.Add(ScriptRunner.CreateScript(filePath, this));
            //todo add scripts from file to load
        }
        /// <summary>
        /// Finds a path to X, Y from the start objects position.
        /// </summary>
        /// <param name="start"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns>Returns a stack containing the path.</returns>
        public Vector<AStarNode> FindPath(GameObject start, int x, int y)
        {
            // init things needed
            PriorityQueue<int, AStarNode> open = new PriorityQueue<int, AStarNode>(false);
            Vector<AStarNode> closed = new Vector<AStarNode>();
            AStarNode current = new AStarNode();
            current.x = (int)start["x"];
            current.y = (int)start["y"];
            if (current.x == 0 || current.y == 0)
                current.F = 0;
            current.F = 0;
            // put first thing open
            open.Enqueue(0, current);
            AStarNode stop = new AStarNode();
            stop.x = x;
            stop.y = y;
            Vector<AStarNode> neighbors = new Vector<AStarNode>();

            // get neighbors, add to array
            while (!(current.Equals(stop)) && open.Size != 0)
            {
                // get the next closest node
                current = open.Dequeue();
                // if its the target, break the loop
                if (current.Equals(stop))
                    break;
                // add the new node current closed list
                closed.Add(current);
                neighbors.Clear();
                AStarNode mod;
                // add all the neighbors to the open list
                for (int xmod = -1; xmod < 2; xmod++)
                {
                    for (int ymod = -1; ymod < 2; ymod++)
                    {
                        GameObject active = this.activeLevel[current.x + xmod][current.y + ymod];
                        if (active.TypeId == "null" && !(xmod == 0 && ymod == 0) || current.x + xmod == stop.x && current.y + ymod == stop.y)
                        {
                            mod = new AStarNode();
                            mod.x = current.x + xmod;
                            mod.y = current.y + ymod;
                            mod.parent = current;
                            if (!closed.Contains(mod))
                                neighbors.Add(mod);
                        }
                    }
                }
                foreach (AStarNode test in neighbors)
                {
                    bool testing = closed.Contains(test);
                    if (!closed.Contains(test))
                    {
                        mod = new AStarNode();
                        mod.x = test.x;
                        mod.y = test.y;
                        mod.parent = test.parent;
                        mod.G = current.G + (int)(Math.Sqrt(Math.Pow(current.x - mod.x, 2) + Math.Pow(current.y - mod.y, 2)) * 10);
                        mod.H = (Math.Abs(stop.x - mod.x) + Math.Abs(stop.y - mod.y)) * 10;
                        mod.F = mod.G + mod.H;
                        open.Enqueue(mod.F, mod);
                    }
                }
            }
            // reconstruct the path in a stack like format.
            if (current.Equals(stop))
            {
                Vector<AStarNode> path = new Vector<AStarNode>();
                while (current.parent != null)
                {
                    path.Add(current);
                    current = current.parent;
                }
                return path;
            }
            else
                return null;
        }
        /// <summary>
        /// A class to represent AStar nodes.
        /// </summary>
        public class AStarNode
        {
            public int F = 0;
            public int H = 0;
            public int G = 0;
            public AStarNode parent;
            private Point point;
            public int x
            {
                get { return this.point.x; }
                set { this.point.x = value; }
            }
            public int y
            {
                get { return this.point.y; }
                set { this.point.y = value; }
            }
            public AStarNode()
            {
                this.parent = null;
            }
            public override bool Equals(object obj)
            {
                if (obj is AStarNode)
                    return this.Equals((AStarNode)obj);
                return base.Equals(obj);
            }
            public bool Equals(AStarNode obj)
            {
                return this.x == obj.x && this.y == obj.y;
            }
            public override string ToString()
            {
                return "(" + this.x + ", " + this.y + ")";
            }
        }
        /// <summary>
        /// A class to represent a simple vector.
        /// </summary>
        public struct Point
        {
            public int x;
            public int y;
        }

        public LoopManager GetLoopManager()
        {
            return this.loopManager;
        }
        public WorldFrameLooper WFL
        {
            get { return this.wfl; }
        }

    }
}
